package com.codrim.cs.data.server.rpc;

import com.alibaba.dubbo.config.annotation.Service;
import com.codrim.common.utils.exception.Assert;
import com.codrim.cs.data.rpc.AdvertiserDataService;
import com.codrim.cs.data.rpc.dto.param.AdvertiserWholeDayDataForPeriodParamDTO;
import com.codrim.cs.data.rpc.dto.param.WholeDayDataParamDTO;
import com.codrim.cs.data.rpc.dto.result.AdvertiserWholeDayDataForPeriodResultDTO;
import com.codrim.cs.data.rpc.dto.result.RealTimeDataDTO;
import com.codrim.cs.data.rpc.dto.result.WholeDayDataResultDTO;
import com.codrim.cs.data.rpc.enums.TimeDimension;
import com.codrim.cs.data.server.dao.RealTimeDataDao;
import com.codrim.cs.data.server.dao.entity.MergeDataRecordDO;
import com.codrim.cs.data.server.dao.entity.StatCsAdvAllDO;
import com.codrim.cs.data.server.dao.mapper.DataRecordMapper;
import com.codrim.cs.data.server.dao.mapper.StatCsAdvAllMapper;
import com.codrim.cs.data.server.dao.realtime.RealTimeData;
import com.codrim.cs.data.server.subservice.DataSubService;
import com.codrim.cs.data.server.subservice.TimeZoneSubService;
import com.codrim.cs.data.server.utils.TimeDimensionUtils;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import static com.codrim.cs.data.server.subservice.ReportUtils.timeDimensionWithSdate;

/**
 * implementation select {@link AdvertiserDataService}
 *
 * @author Liang.Zhuge on 26/07/2017.
 */
@Service(version = AdvertiserDataService.RPC_VERSION)
public class AdvertiserDataServiceImpl implements AdvertiserDataService {
    private static final Logger logger = LoggerFactory.getLogger(AdvertiserDataServiceImpl.class);

    @Resource TimeZoneSubService timeZoneSubService;
    @Resource StatCsAdvAllMapper dayStatCsAdvAllMapper;
    @Resource DataRecordMapper dataRecordMapper;
    @Resource RealTimeDataDao realTimeDataDao;
    @Resource DataSubService dataSubService;

    @Override
    public List<RealTimeDataDTO> advertiserWholeDayRealData(WholeDayDataParamDTO param) {
        logger.info("Advertiser whole day real data, param:{}", param);
        Assert.notNull(param, "Invalid param, can not be null");

        final Integer companyId = param.getCompanyId();
        Date date = param.getDate();

        Assert.notNull(companyId, "Invalid param, companyId field can not be null");
        Assert.notNull(date, "Invalid param, date can be null");

        Instant instant = date.toInstant();
        Instant begin = instant.truncatedTo(ChronoUnit.DAYS);
        Instant end = begin.plus(23, ChronoUnit.HOURS).plus(59, ChronoUnit.MINUTES).plus(59, ChronoUnit.SECONDS);
        logger.info("Advertiser whole day real data, begin:{}, end:{}", begin, end);

        return realTimeDataDao.advertiserRealTimeData(companyId, begin, end)
                .stream().map(RealTimeData::toDTO).collect(Collectors.toList());
    }

    @Override
    public List<WholeDayDataResultDTO> advertiserWholeDayData(WholeDayDataParamDTO param) {
        logger.info("Advertiser whole day data, param:{}", param);
        List<WholeDayDataResultDTO> dto = dataSubService.wholeDayDataForAdv(param,
                dayStatCsAdvAllMapper::findStatCsAdvAllForDashboard,
                dataRecordMapper::findDataRecordForDashboard);
        logger.info("Advertiser whole day data, data:{}", dto);
        return dto;
    }

    /**
     *该接口,处理比较别扭.前端传递的时间区间有要求
     * 当天: start=用户操作的当前时间, end=用户操作的当天23:59:59
     * 其他时时间: start=指定天的23:59:59, end=指定天的23:59:59
     *
     * 示例:
     *  1.假设当前东八区时间为 2018-02-05 7:30 查询当天数据 参数为 start = 2018-02-05 7:30 end = 2018-02-05 23:59
     *      当前用户的时区设置是零时区, 则统计区间为2018020400-2018920423
     *  2.假设当前东八区时间为 2018-02-05 7:30 查询昨天数据 参数为 start = 2018-02-04 23:59 end = 2018-02-04 23:59
     *      当前用户的时区设置是零时区, 则统计区间为2018020400-2018920423
     *  3.假设当前东八区时间为 2018-02-05 8:30 查询当天数据 参数为 start = 2018-02-05 8:30 end = 2018-02-05 23:59
     *      当前用户的时区设置是零时区, 则统计区间为2018020500-2018920501
     * @param param 查询参数
     * @return 统计数据
     */
    @Override
    public AdvertiserWholeDayDataForPeriodResultDTO advertiserWholeDayDataForPeriod(AdvertiserWholeDayDataForPeriodParamDTO param) {
        final Date begin = param.getBegin();
        final Date end = param.getEnd();
        final Integer advertiserId = param.getAdvertiserId();
        final Integer zone = param.getZone();
        logger.info("Params, advertiserId:{}, sZone:{}, begin:{}, end:{}", advertiserId, zone, begin, end);
        final AdvertiserWholeDayDataForPeriodResultDTO result = new AdvertiserWholeDayDataForPeriodResultDTO();
        boolean isSearchOneDay = begin.getDate() == end.getDate() && begin.getMonth() == end.getMonth();
        TimeDimension timeDimension = isSearchOneDay ? TimeDimension.Hour : TimeDimension.Day;
        int iBegin, iEnd;
        /*
         * 处理比较难理解,可参考上文示例说明(和前端参数格式绑定在了一起)
         */
        Date targetBeginDate = DateUtils.addHours(begin, zone - 8);
        /* 统计区间从目标时区 0时 点开始 */
        targetBeginDate.setHours(0);
        iBegin = dateToInt(targetBeginDate, timeDimension);
        if (timeDimension != TimeDimension.Hour) {
            targetBeginDate = DateUtils.addHours(end, zone - 8);
        }
        targetBeginDate.setHours(23);
        iEnd = dateToInt(targetBeginDate, timeDimension);

        int iNow = dateToInt(DateUtils.addHours(new Date(System.currentTimeMillis()), zone - 8), timeDimension);
        List<StatCsAdvAllDO> datas = dayStatCsAdvAllMapper.findByAdvIdAndPeriod(
                param, iBegin, iEnd, timeZoneSubService.prefixTableNameForTimeZone(zone), timeDimension.key);
        logger.info("iBegin:{},iNow:{}, iEnd:{},data seize:{}", iBegin, iNow, iEnd, Optional.ofNullable(datas).map(List::size).orElse(0));

        if (iNow >= iBegin && iNow <= iEnd) {
            logger.info("Include now, try to get real time data");
            List<RealTimeData> realTimeDatas = realTimeDataDao.advertiserRealTimeData(advertiserId, begin.toInstant(), end.toInstant());
            boolean nowData = datas.stream().anyMatch(item -> item.getSdate() == iNow);
            if (CollectionUtils.isNotEmpty(realTimeDatas) && nowData) {
                logger.info("Real time data exists");
                List<RealTimeDataDTO> realTimeDto = realTimeDatas.stream()
                        .map(RealTimeData::toDTO)
                        .collect(Collectors.toList());
                result.setRealTimeDataDTOList(realTimeDto);
            }
        }
        //查出录入数据
        final Integer beginForDataRecord = timeDimensionWithSdate(iBegin, param.getZone(), TimeDimension.Day).getSdate();
        final Integer endForDataRecord = timeDimensionWithSdate(iEnd, param.getZone(), TimeDimension.Day).getSdate();
        final List<MergeDataRecordDO> mergeDataRecordDOS = dataRecordMapper.findByAdvIdAndPeriod(param,
                beginForDataRecord, endForDataRecord, timeZoneSubService.prefixTableNameForTimeZone(zone));

        final List<WholeDayDataResultDTO> dataRecordWholeDayDataResultDTO = Optional.ofNullable(mergeDataRecordDOS)
                .map(List::stream)
                .map(s -> s.map(MergeDataRecordDO::toWholeDayDataResultDTO))
                .map(s -> s.collect(Collectors.toList()))
                .orElse(null);

        List<WholeDayDataResultDTO> wholeDayDataResultDTOS = Optional.ofNullable(datas)
                .map(List::stream)
                .map(s -> s.map(StatCsAdvAllDO::toWholeDayDataResultDTO))
                .map(s -> s.collect(Collectors.toList())).orElse(null);

        result.setWholeDayDataResultDTOList(union(wholeDayDataResultDTOS, dataRecordWholeDayDataResultDTO));
        return result;
    }

    private List<WholeDayDataResultDTO> union(List<WholeDayDataResultDTO> l1, List<WholeDayDataResultDTO> l2) {
        List<WholeDayDataResultDTO> tmp = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(l1)) {
            tmp.addAll(l1);
        }
        if (CollectionUtils.isNotEmpty(l2)) {
            tmp.addAll(l2);
        }
        return CollectionUtils.isNotEmpty(tmp) ? tmp : null;
    }

    private int dateToInt(Date date, TimeDimension timeDimension) {
        return TimeDimensionUtils.dateToInt(date, timeDimension);
    }
}
